import { Client, Socket, Session, Match, User } from "@heroiclabs/nakama-js";
import { Player } from "./Player";
import { ApiAccount } from "@heroiclabs/nakama-js/dist/api.gen";

export class NakamaError extends Error {
  code: number;

  constructor(message: string, code: number) {
    super(message);
    this.name = 'NakamaError';
    this.code = code;
  }
}

// 封装的错误处理函数
export async function handleNakamaError(error: any): Promise<never> {
  try {
    const errJson = await error.json();
    const { message, code } = errJson;
    console.error("Nakama error:", message, "code:", code);
    throw new NakamaError(message, code);
  } catch (e) {
    // fallback：不是标准json格式
    throw new NakamaError(error.message, -1);
  }
}

export class NakamaManager {
  private static instance: NakamaManager;
  public client: Client;
  public socket: Socket = null;
  public session: Session | null = null;

  public match: Match | null = null;         
  public user: ApiAccount = null;        


  private constructor() {
    this.client = new Client("defaultkey", "localhost", "7350", false, 10000, true);

    this.socket = this.client.createSocket(false);
    this.socket.onerror = (error) => {
      console.error("Socket error:", error);
    };

    this.socket.onmatchdata = (matchState) => {
      console.log("Socket data:", matchState);
    };

    this.socket.onmatchpresence = (matchPresence) => {
      console.log("Socket presence:", matchPresence);
    };

    this.socket.ondisconnect = (event) => {
      console.log("Socket close :", event);
    };

  }

  public static getInstance(): NakamaManager {
    if (!NakamaManager.instance) {
      NakamaManager.instance = new NakamaManager();
    }
    return NakamaManager.instance;
  }

  public async autoSignIn(): Promise<boolean> {
    // 尝试从 localStorage 中恢复会话
    const token = localStorage.getItem("session_token");
    const refresh_token = localStorage.getItem("session_refresh_token");
    if (!token) {
      throw new Error(
        "token is not exist, need to signin"
      );
    }

    try {
      var session = Session.restore(token, refresh_token);
      if (!session) {
        throw new Error(
          "session restore error, need to signin again"
        );
      }

      if (session.isexpired) {
        session = await this.client.sessionRefresh(session);
        console.log("Session refreshed successfully", session);
      }
      this.user = await this.client.getAccount(session)
      this.session = await this.socket.connect(session, true);

      this.saveSessionToLocalStorage();

      return true;
    } catch (error) {
      const errJson = await error.json();
      const { message, code } = errJson;
      console.error("Nakama error:", message, "code:", code);
      throw new NakamaError(message, code);
    }
  }

  // 使用用户名和密码登录
  public async loginWithEmail(email: string, password: string) {
    if (!email || !password) {
      throw new Error("Email and password are required for login.");
    }

    try {
      const session: Session = await this.client.authenticateEmail(
        email,
        password,
        true
      );

      if (!session) {
        throw new Error(
          "Authentication failed. Please check your email and password."
        );
      }
      this.session = session;
      this.saveSessionToLocalStorage();
      this.user = await this.client.getAccount(session)
      await this.socket.connect(session, true);
      console.log("Login successful", session);
    } catch (error) {
      const errJson = await error.json();
      const { message, code } = errJson;
      console.error("Nakama error:", message, "code:", code);
      throw new NakamaError(message, code);
    }
  }


  saveSessionToLocalStorage() {
    if (this.session) {
      localStorage.setItem("session_token", this.session.token);
      localStorage.setItem("session_refresh_token", this.session.refresh_token);
      console.log("Session saved to localStorage");
    }
  }

  // 创建德扑游戏
  // initialBuyIn: 初始买入金额，默认为200 大盲: 200/100 = 2 小盲：200/200 = 1
  // maxBuyIn: 最大买入金额，默认为2000
  // maxPlayerCount: 最大玩家数量，默认为9
  // dismissAfterSec: 游戏自动解散时间，默认为1800秒（30分钟）
  // 返回 RoomCode
  public async createPokerGame(
    roomName: string = "Poker Game",
    initialBuyIn: number = 200,
    maxBuyIn: number = 2000,
    maxPlayerCount: number = 9,
    dismissAfterSec: number = 1800
  ): Promise<string> {
    if (!this.session) {
      throw new Error("Session is not available. Please log in first.");
    }
    try {
      const payload = {
        roomName: roomName,
        initialBuyIn: initialBuyIn,
        maxBuyIn: maxBuyIn,
        maxPlayerCount: maxPlayerCount,
        dismissAfterSec: dismissAfterSec,
      };
      const response = await this.client.rpc(
        this.session,
        "CreatePokerGame",
        payload
      );
      const match_id = response.payload["match_id"];
      const room_code = response.payload["room_code"];
      console.log("create game successful, room_code:%o, match_id:%o", room_code, match_id);
      return match_id;
    } catch (error) {
      console.error("Error: %o", error.message);
      throw error;
    }
  }

  // 加入德扑游戏
  // room_id: 房间ID
  public async joinPokerGame(room_code: string) {
    if (!this.session) {
      throw new Error("Session is not available. Please log in first.");
    }
    try {
      const payload = { room_code: room_code };
      const response = await this.client.rpc(
        this.session,
        "GetPokerGame",
        payload
      );
      const match_id = response.payload["match_id"];
      const match = await this.socket.joinMatch(match_id);
      this.match = match;
      console.log(`Joined game: ${match.match_id}, label: ${match.label}`);
    } catch (error) {
      console.error("Error: %o", error.message);
      throw error;
    }
  }

  // 加入德扑游戏
  // match_id: 比赛id
  public async joinPokerGameWithMatchID(match_id: string) {
    if (!this.session) {
      throw new Error("Session is not available. Please log in first.");
    }
    try {
      const match = await this.socket.joinMatch(match_id);
      this.match = match;
      console.log("match:", match);
      console.log(`Joined game: ${match.match_id}, label: ${match.label}`);
    } catch (error) {
      console.error("Error: %o", error.message);
      throw error;
    }
  }
}
